define('framework/groupgrid/GillionGroupGridModule', [
    'angular',
    'jquery',
    'underscore',
    'artTmpl',
    'ngContextMenu',
    'framework/colGroupSettings/ColGroupSettingsModule',
    'framework/groupDataSource/GroupDataSourceModule',
    'framework/clickbox/GillionClickBoxModule',
    'framework/gridlayout/GridLayoutModule'
], function (angular, $, _, artTmpl) {
    var CONSTANTS = {
            CHECK_ROW_WIDTH: 40,
            SPACE_CELL_WIDTH: 19,
            MIN_CELL_WIDTH: 40,
            DATA_COLUMN_INSTANCE_KEY: '$column-ins',
            REFRESH_ROWS: '$grid-refresh-rows'
        },
        ORDER = {
            ASC: 'asc',
            DESC: 'desc'
        },
        isIE = navigator.appName == "Microsoft Internet Explorer";


    return angular.module('GillionGroupGridModule', ['GroupDataSourceModule', 'ColGroupSettingsModule', 'GillionClickBoxModule', 'ng-context-menu', 'GridLayoutModule'])
        .directive('gGroupGrid', function ($parse, $filter, $window, $document, $compile, ColGroupSettings, gridLayoutService, Arrays, Functions, LocalStorages, GroupDataSources) {

                function _toWidthClassBlock(className, width) {
                    return '.' + className + '{width:' + width + 'px;}'
                }

                /**
                 * 将 Array 中未定义的位置用余数的平均值填充, 如果平均值比最小值小， 用最小值填充
                 * @example paddingWidthSpentAvg([1, undefined, 3, 4, undefined], 20, 2) -> [1, 6, 3, 4, 6]
                 * @example paddingWidthSpentAvg([1, undefined, 3, 4, undefined], 20, 7) -> [1, 7, 3, 4, 7]
                 * @param array {Array<Number>} 需要填充的数组
                 * @param total {Number} 总值
                 * @param min {Number} 最小值
                 */
                function paddingWithSpentAvg(array, total, min) {
                    var definedEls = Arrays.filter(array, angular.isDefined),
                        definedSum = Arrays.isNotEmptyArray(definedEls) ? Arrays.reduce(definedEls, Functions.sum) : 0,
                        undefinedCount = array.length - definedEls.length,
                        spent = total - definedSum,
                        spentAvg = spent / undefinedCount,
                        padding = Math.max(spentAvg, min);
                    angular.forEach(array, function (el, index) {
                        if (angular.isUndefined(el)) {
                            array[index] = padding;
                        }
                    });
                }

                function getColClassReg(colWidthClassName) {
                    var matched = colWidthClassName.match(/grid-(.+)-col-(\d+)/),
                        colIndex = matched[2];
                    return new RegExp('(?:\\' + colWidthClassName + '|\\\.grid-' + matched[1] + '-col-[0-' + colIndex + ']-[' + (colIndex + 1) + '-9])\\\{width:([\\\d\\\.]+)px;}', 'g');
                }

                function GroupGrid(scope, element, attrs) {
                    var me = this;
                    me.scope = scope;
                    me.element = element;
                    me.attrs = attrs;
                    me.width = element.width();
                    me.hash = scope.$id;
                    me.widthStyleBlock = element.children('style')[0];

                    me.cellValueGetterCache = {};
                    me.scopeEventCache = {};
                    scope.columns = me.columns = [];
                    if (attrs.$columns) {
                        scope.$columns = me.columns;
                    }
                    me.footCellIndex = 0;
                    me.actions = {};

                    me.$headerBox = me.element.find('thead');
                    me.$colResizeLine = me.element.find('div.col-resize-line');

                    me.tbody = me.element.find('tbody')[0];
                    if (me._hasCheckedBox()) {
                        me.scope.checkedRows = [];
                    }
                }

                GroupGrid.prototype = {

                    _hasCheckedBox: function (){
                        return !!this.attrs['checkedRows'];
                    },

                    _registerHeaderDraggable: function () {
                        var me = this;
                        if (!me.scope.colSettingsKey) {
                            return;
                        }
                        var $headerBox = me.$headerBox,
                            $headerInput = $headerBox.find(':input'),
                            $colResizeLine = me.$colResizeLine,
                            preventInputFocus = function () {
                                $headerInput.each(function (i, el) {
                                    el.blur();
                                })
                            }
                            mousemoveFun = _.throttle(function ($event) {
                                var target = $event.target,
                                    $th = angular.element(target).closest('th'),
                                    width, offset, eX, inRightEdge, dragging;
                                if ($th.length > 0) {
                                    width = $th.outerWidth();
                                    offset = $th.offset();
                                    eX = $event.clientX;
                                    inRightEdge = (eX - offset.left) > (width - 5);
                                    dragging = angular.isDefined($headerBox.data('_start_x'));
                                    if (!$(target).is('i[data-col-settings]') && (inRightEdge || dragging)) {
                                        $headerBox.addClass('resizeable');
                                        if (me.scope.draggable) {
                                            $th.attr("draggable", "false");
                                        }
                                        if (dragging) {
                                            $headerInput.on('focus', preventInputFocus);
                                            $colResizeLine.css('left', $event.clientX + me.element.scrollLeft() - me.element.offset().left);
                                        }
                                    } else {
                                        $headerBox.removeClass('resizeable');
                                        if (me.scope.draggable) {
                                            $th.attr("draggable", "true");
                                        }
                                    }
                                }
                            }, 50);
                        me.element.on('mouseup', function () {
                            $headerInput.off('focus', preventInputFocus);
                        });
                        $headerBox.mousemove(mousemoveFun);
                        $headerBox.mousedown(function ($event) {
                            var target = $event.target,
                                $th = angular.element(target).closest('th');
                            if ($th.length > 0 && $headerBox.hasClass('resizeable')) {
                                me._startColResize($event, $th);
                                angular.element($document).one('mouseup', function ($event) {
                                    me._finishColResize($event, $th);
                                });
                            }
                        });

                        /** 头部输入框的时候 可以拖动 begin **/
                        if ($headerBox.find("input").size() > 0) {
                            var $input = $headerBox.find("input"),
                                $th = $input.parents("th");

                            $input.bind("focus", function () {
                                $headerBox.unbind("mousemove");
                                if (me.scope.draggable) {
                                    $th.attr("draggable", "false");
                                }
                            });
                            $input.bind("blur", function ($event) {
                                if (me.scope.draggable) {
                                    $th.attr("draggable", "false");
                                    $headerBox.mousemove(mousemoveFun);
                                }
                            });
                        }
                        /** 头部输入框的时候 可以拖动  end **/
                    },

                    _startColResize: function ($event) {
                        $('body').css({
                            webkitUserSelect: 'none',
                            mozUserSelect: 'none',
                            msUserSelect: 'none',
                            userSelect: 'none'
                        });
                        var me = this,
                            $headerBox = me.$headerBox,
                            $colResizeLine = me.$colResizeLine;
                        $colResizeLine.css('left', $event.clientX + me.element.scrollLeft() - me.element.offset().left);
                        // $colResizeLine.css('left', $event.clientX - me.element.offset().left + me.element.children('div.grid-body').scrollLeft());
                        $colResizeLine.show();
                        $headerBox.data('_start_x', $event.clientX);
                        $window.document.onselectstart = function () {
                            return false;
                        };
                    },

                    _finishColResize: function ($event, $th) {
                        $('body').css({
                            webkitUserSelect: 'auto',
                            mozUserSelect: 'auto',
                            msUserSelect: 'auto',
                            userSelect: 'auto'
                        });
                        var me = this,
                            $colResizeLine = me.$colResizeLine,
                            $headerBox = me.$headerBox,
                            startX = $headerBox.data('_start_x'),
                            endX = $event.clientX,
                            added = endX - startX,
                            originW = $th.outerWidth(),
                            headerScope = $th.isolateScope();
                        if (headerScope) {
                            var col = headerScope.column,
                                colWidthClassName = col.colWidthClassName,
                                $styleBlock = me.element.children('style'),
                                oldStyleContent = $styleBlock.html(),
                                newStyleContent;
                            newStyleContent = oldStyleContent.replace(getColClassReg(colWidthClassName), function (block, width) {
                                var newWidth = Number(width) + added;
                                newWidth = _.isNaN(newWidth) ? CONSTANTS.MIN_CELL_WIDTH : newWidth;
                                newWidth = Math.max(newWidth, CONSTANTS.MIN_CELL_WIDTH);
                                col.width = newWidth;
                                return block.replace(width, newWidth);
                            });
                            $styleBlock.html(newStyleContent);
                            me._reCalculateWidth();
                        }
                        $colResizeLine.hide();
                        $headerBox.removeData('_start_x');

                        $window.document.onselectstart = angular.noop;
                    },

                    /**
                     * 重新渲染列 `title`
                     * @param rowData {T} 记录
                     * @param column {object} Column对象
                     * @param cell {HTMLTableCellElement} 单元格 Dom
                     * @private
                     */
                    _renderCellTitle: function (rowData, column, cell) {
                        var me = this,
                            cellValue = me.getCellValue(rowData, column);
                        if (cellValue) {
                            angular.element(cell).attr('title', cellValue);
                        }
                    },
                    _addColumn: function (column) {
                        this._renderColumnFilter(column);
                        return this.columns.push(column);
                    },
                    _renderColumnFilter: function (column) {
                        var me = this,
                            scope = me.scope,
                            filter = column.filter,
                            $eleCol = column.element,
                            filterTmpl = column.filterTmpl,
                            condition = me.filterCondition || (me.filterCondition = {}),
                            $filter;

                        if (!filterTmpl) {
                            if (!filter) return;
                            switch (filter) {
                                case 'text':
                                    filterTmpl = '<input type="text" class="form-text" />';
                                    break;
                                case 'number-spinner':
                                    filterTmpl = '<g-number-spinner></g-number-spinner>';
                                    break
                            }
                        }
                        if (!filterTmpl) return;
                        $filter = angular.element(filterTmpl);
                        $filter.attr({
                            'ng-model': 'filter.' + column.field
                        });
                        $eleCol.parent().addClass('has-filters');
                        $eleCol.append($filter);
                        $compile($filter)(scope);
                        $filter.wrap('<div class="column-filter"></div>');
                        condition[column.field] = column.filterCondition || 'EQ';
                    },
                    _getCellValue: function (row, column) {
                        var me = this,
                            cellValueGetterCache = me.cellValueGetterCache;
                        if (!cellValueGetterCache[column.data]) {
                            cellValueGetterCache[column.data] = me.generateCellValueGetter(column);
                        }
                        return cellValueGetterCache[column.data](row);
                    },
                    generateCellValueGetter: function (column) {
                        if (column.data) {
                            return $parse(column.data);
                        }
                        return angular.noop;
                    },

                    _getSortName: function (sortingIcon) {
                        var me = this,
                            columnIndex = me._getColumnIndex(sortingIcon.closest('th'));
                        return me.columns[columnIndex].sortable;
                    },

                    _isSort: function (sortName, sortDirection) {
                        var me = this,
                            sortNames = me.sortName || [],
                            sortDirections = me.sortDirection || [],
                            index = _.indexOf(sortNames, sortName);
                        if (index < 0) return false;
                        return sortDirections[index] == sortDirection;
                    },

                    _sortIndex: function (sortName) {
                        var me = this,
                            sortNames = me.sortName || [],
                            index = _.indexOf(sortNames, sortName),
                            len = sortNames.length;
                        if (index > -1 && len > 1) {
                            return index + 1;
                        } else {
                            return '';
                        }
                    },

                    _specifySort: function ($event, $sortingIcon, multiSort) {
                        var me = this,
                            $header = $sortingIcon.closest('th'),
                            column = $header.isolateScope().column,
                            newSortName = column.field,
                            newSortDirection = $sortingIcon.hasClass('caret-up') ? ORDER.ASC : ORDER.DESC,
                            sortNames = me.sortName = me.sortName || [],
                            sortDirections = me.sortDirection = me.sortDirection || [],
                            index = _.indexOf(sortNames, newSortName),
                            oldSortDirection;
                        if (multiSort) {
                            if (index > -1) {
                                sortDirections[index] = newSortDirection;
                            } else {
                                sortNames.push(newSortName);
                                sortDirections.push(newSortDirection);
                            }
                        } else {
                            if (sortNames.length == 1 && index > -1) {
                                oldSortDirection = sortDirections[index];
                                if (oldSortDirection === newSortDirection) {
                                    sortNames = [];
                                    sortDirections = [];
                                } else {
                                    sortNames = [newSortName];
                                    sortDirections = [newSortDirection];
                                }
                            } else {
                                sortNames = [newSortName];
                                sortDirections = [newSortDirection];
                            }
                        }
                        me._doSort(sortNames, sortDirections);
                    },

                    _toggleSort: function ($event, multiSort) {
                        var me = this,
                            $target = angular.element($event.target),
                            $header = $target.closest('th'),
                            isolateScope = $header.isolateScope(),
                            sortNames = me.sortName = me.sortName || [],
                            sortDirections = me.sortDirection = me.sortDirection || [],
                            column, newSortName, oldSortDirection, newSortDirection, index;
                        if (!(isolateScope && isolateScope.column && isolateScope.column.sortable)) return;

                        column = isolateScope.column;
                        newSortName = column.field;
                        newSortDirection = ORDER.ASC;
                        index = _.indexOf(sortNames, newSortName);

                        if (multiSort) {
                            if (index > -1) {
                                oldSortDirection = sortDirections[index];
                                if (oldSortDirection === ORDER.ASC) {
                                    sortDirections[index] = ORDER.DESC;
                                } else if (oldSortDirection === ORDER.DESC) {
                                    sortNames.splice(index, 1);
                                    sortDirections.splice(index, 1);
                                }
                            } else {
                                sortNames.push(newSortName);
                                sortDirections.push(newSortDirection);
                            }
                        } else {
                            if (sortNames.length == 1 && index > -1) {
                                oldSortDirection = sortDirections[index];
                                if (oldSortDirection === ORDER.ASC) {
                                    sortDirections[index] = ORDER.DESC;
                                } else if (oldSortDirection === ORDER.DESC) {
                                    sortNames = [];
                                    sortDirections = [];
                                }
                            } else {
                                sortNames = [newSortName];
                                sortDirections = [newSortDirection];
                            }
                        }
                        me._doSort(sortNames, sortDirections);
                    },

                    _doSort: function (sortName, sortDirection) {
                        if (this.__rendered !== true) return;
                        var me = this,
                            scope = me.scope,
                            onBeforeSortColumn = scope.onBeforeSortColumn || angular.noop,
                            onSortColumn = scope.onSortColumn || angular.noop,
                            dataSource = me.dataSource,
                            sortEventParam = {
                                grid: this,
                                sortName: sortName,
                                sortDirection: sortDirection
                            };
                        if (onBeforeSortColumn(sortEventParam) !== false) {
                            //为了触发数据源请求
                            dataSource.sortName = me.sortName = sortName;
                            dataSource.sortDirection = me.sortDirection = sortDirection;
                            var allowAutoLoad = dataSource.$scope.allowAutoLoad;
                            if (!(allowAutoLoad === 'true' || allowAutoLoad === true)) {
                                dataSource.doRequestData();
                            }
                            onSortColumn(sortEventParam);
                        }
                    },

                    _desortIfSameAsOld: function (newSortName, newDirection) {
                        var me = this,
                            oldSortName = me.sortName,
                            oldSortDirection = me.sortDirection;
                        if (oldSortName === newSortName && oldSortDirection === newDirection) {
                            return undefined;
                        }
                        return newDirection;
                    },

                    isAllRowsChecked: function () {
                        return this.scope.allChecked;
                    },

                    _toggleAllChecked: function($event, $target) {
                        var me = this;
                        if ($target.is('thead .grid-col-checkbox .form-clickbox a.fi')) {
                            if (!me.scope.allChecked) {
                               me.scope.checkedRows = [];
                               me._updateRowCheckbox();
                            } else {
                                me._checkAll();
                                me._updateRowCheckbox();
                            }
                        }
                    },

                    _checkAll: function() {
                        var me = this;
                        var $trs = $(me.tbody).find('tr');
                        var checkedRows = [];
                        $trs.each(function(i, tr) {
                            var $tr = $(tr);
                            var nodeId = $tr.data('node-id');
                            var record;
                            if (nodeId) {
                                var node = me.dataSource.$node(nodeId);
                                record = node.origin;
                            } else {
                                record = me.source[i];
                            }
                            checkedRows.push(record);
                        });
                        me.scope.checkedRows = checkedRows;
                    },

                    _toggleRowChecked: function($event, $target) {
                        var me = this;
                        if ($target.is('tbody .grid-col-checkbox .form-clickbox a.fi')) {
                            var $row = $target.closest('tr');
                            var trs = $(me.tbody).find('tr');
                            var nodeId = $row.data('node-id');
                            var node, rowIndex, record;
                            if (nodeId) {
                                node = me.dataSource.$node(nodeId);
                                record = node.origin;
                            } else {
                                rowIndex = $row.index();
                                record = me.source[rowIndex];
                            }
                            var isChecked = false;
                            var checkedRows = [];

                            _.forEach(me.scope.checkedRows, function(row, index) {
                                var checkedNodeId = me._getNodeId(row);
                                var $checkedTr;
                                var checkedRowIndex = _.findIndex(me.source, row);
                                if (nodeId) {
                                    $checkedTr = $(me.tbody).find('tr[data-node-id="' + checkedNodeId + '"]');
                                } else {
                                    $checkedTr = $(me.tbody).eq(checkedRowIndex);
                                }
                                if (nodeId && nodeId === checkedNodeId) {
                                    isChecked = true;
                                } else if (!nodeId && rowIndex === checkedRowIndex) {
                                    isChecked = true;
                                } else {
                                    checkedRows.push(row);
                                }
                            });
                            if (!isChecked) {
                                checkedRows.push(record);
                            }
                            me.scope.checkedRows = checkedRows;
                            me._updateRowCheckbox();
                        }
                    },

                    _updateRowCheckbox: function() {
                        var me = this;
                        var checkedNodeIds = me._getCheckedNodeIds();
                        var $trs = $(me.tbody).find('tr');
                        var allChecked = true;
                        var checkedRowIndex = [];
                        _.forEach(me.scope.checkedRows, function(record) {
                            checkedRowIndex.push(_.findIndex(me.source, record));
                        });
                        if (!checkedRowIndex.length) {
                            allChecked = false;
                        }
                        console.log(checkedRowIndex);
                        console.log('checkedRows:');
                        $trs.each(function(i, tr) {
                            var $tr = $(tr);
                            var nodeId = $tr.data('node-id');
                            var $checkbox;
                            if (nodeId) {
                                $checkbox = $tr.find('.grid-col-checkbox').find('.form-clickbox');
                            } else {
                                $checkbox = $tr.find('.form-clickbox');
                            }
                            if (nodeId && _.indexOf(checkedNodeIds, nodeId) < 0) {
                                $checkbox.removeClass('selected');
                                allChecked = false;
                            } else if (!nodeId && _.indexOf(checkedRowIndex, i) < 0) {
                                $checkbox.removeClass('selected');
                                allChecked = false;
                            } else {
                                $checkbox.addClass('selected');
                            }
                        });
                        me.scope.allChecked = allChecked;
                    },

                    _getCheckedNodeIds: function() {
                        var me = this;
                        var nodeIds = [];
                        _.forEach(me.scope.checkedRows, function(row) {
                            nodeIds.push(me._getNodeId(row));
                        });
                        return nodeIds;
                    },

                    _headClick: function ($event) {
                        var me = this,
                            target = $event.target,
                            $target = angular.element(target),
                            multiSort = $event.ctrlKey,
                            $columnFilter = $target.closest('.column-filter').not($target);
                        if ($columnFilter.length) {
                            return;
                        }
                        if ($target.is('span.caret')) {
                            me._specifySort($event, $target, multiSort);
                        } else if (!me.$headerBox.hasClass('resizeable')) {
                            me._toggleSort($event, multiSort);
                        }
                    },

                    _rowClick: function ($event, $row) {
                        var me = this,
                            scope = me.scope,
                            rowIndex = $row[0].rowIndex,
                            oldSelected = scope.selectedRow,
                            selectedRecord = scope.source[rowIndex],
                            isSelected = !(scope.canToggleSelected === 'true' && oldSelected === selectedRecord),
                            onBeforeSelect = scope.onBeforeSelect,
                            onSelect = scope.onSelect,
                            selectParam = {
                                $event: $event,
                                record: selectedRecord,
                                grid: me,
                                $row: $row,
                                isSelected: isSelected
                            };

                        //拖选功能start
                        if (me.scope.dragSelected && me.scope.dragSelected.length > 0) {
                            for (var i = 0; i < me.scope.dragSelected.length; i++) {
                                var index = me.scope.dragSelected[i];
                                angular.element(me.tbody.rows[index]).removeAttr("active");
                            }
                        }
                        //end拖选功能

                        if (onBeforeSelect(selectParam) !== false) {
                            scope.selectedRow = isSelected ? selectedRecord : undefined;
                            if (me.__oldSelectedRow) {
                                me.__oldSelectedRow.removeAttr('active');
                            }
                            if (isSelected) {
                                $row.attr('active', true);
                            }
                            me.__oldSelectedRow = $row;
                            onSelect(selectParam);
                        }

                    },

                    _rowDbClick: function ($event, $row) {
                        var me = this,
                            scope = me.scope,
                            onBeforeRowDbclick = scope.onBeforeRowDbclick,
                            onRowDbclick = scope.onRowDbclick,
                            nodeId = $row.data('node-id'),
                            rowIndex = $row.index(),
                            record;
                        if (nodeId) {
                            var node = me.dataSource.$node(nodeId);
                            record = node.origin;
                        } else {
                            record = me.source[rowIndex];
                        }
                        eventParam = {
                            $event: $event,
                            record: record,
                            grid: me,
                            $row: $row
                        };
                        console.log(eventParam);
                        if (onBeforeRowDbclick(eventParam) !== false) {
                            onRowDbclick(eventParam);
                        }
                    },

                    _cellDbClick: function ($event, $cell) {
                        var me = this,
                            scope = me.scope,
                            selectedRecord = scope.selectedRow,
                            columnIndex = me._getColumnIndex($cell),
                            column = me.columns[columnIndex],
                            globalBeforeCellDbclick = scope.onBeforeCellDbclick,
                            globalCellDbclick = scope.onCellDbclick,
                            onBeforeCellDbClick = angular.isFunction(column.onBeforeCellDbclick) ? column.onBeforeCellDbclick : angular.noop,
                            onCellDbClick = angular.isFunction(column.onCellDbclick) ? column.onCellDbclick : angular.noop,
                            eventParam = {
                                $event: $event,
                                record: selectedRecord,
                                grid: me,
                                $cell: $cell,
                                column: column
                            };

                        if (globalBeforeCellDbclick(eventParam) !== false) {
                            globalCellDbclick(eventParam);
                        }
                        if (onBeforeCellDbClick(eventParam) !== false) {
                            onCellDbClick(eventParam);
                        }
                    },

                    _getScopeEvent: function (eventDefineAttrVal) {
                        var me = this,
                            scopeEventCache = me.scopeEventCache,
                            gridOuterScope = me.scope.$parent;
                        if (!!eventDefineAttrVal) {
                            if (!scopeEventCache[eventDefineAttrVal]) {
                                var parentGet = $parse(eventDefineAttrVal);
                                return function (locals) {
                                    return parentGet(gridOuterScope, locals);
                                };
                            }
                            return scopeEventCache[eventDefineAttrVal];
                        }
                        return angular.noop;
                    },

                    /**
                     *
                     * @param cellScope.rowIndex
                     * @param cellScope.column
                     * @param cellScope.row
                     * @param cellScope.columnIndex
                     * @returns {void|*}
                     */
                    _getCellTmplScope: function (cellScope) {
                        var me = this,
                            scope = me.scope,
                            gridOuterScope = scope.$parent,
                            rowIndex = cellScope.rowIndex,
                            formName = me.formName,
                            cellTmplScope = angular.extend(gridOuterScope.$new(), {
                                grid: me,
                                column: cellScope.column,
                                rowIndex: rowIndex,
                                columnIndex: cellScope.columnIndex,
                                row: cellScope.row
                            });
                        if (formName) {
                            cellTmplScope[formName] = scope[formName];
                        }
                        return cellTmplScope;
                    },

                    /**
                     * 重置表格状态
                     * 包括：   1、 全选状态
                     *        2、 勾选行
                     *        3、 选中行
                     */
                    _reset: function () {
                        var me = this,
                            scope = me.scope;
                        // 清空选中行
                        scope.selectedRow = scope.$selectedRow = undefined;

                    },

                    _getRowTmpl: function () {
                        var me = this, columnsTmpl;
                        if (!me.rowTmpl) {
                            columnsTmpl = _
                                .chain(me.columns)
                                .filter(angular.isDefined)
                                .map(function (column) {
                                    if (column.hide === true) return '';
                                    var cellTmpl = '<td class="' + column.colWidthClassName + ' ' + column.cellAlignClass + '{{if isGroupingBy(record, "' + column.field + '")}} has-icon{{/if}}' + '"';
                                    if (column.tmpl) {
                                        cellTmpl += 'cell-templated><span data-role="display" ></span>'
                                    } else if (column.data) {
                                        cellTmpl += 'title="{{record.' + column.data + '}}"><span data-role="display">{{record.' + column.data + '}}</span>'
                                    } else {
                                        cellTmpl += '>'
                                    }
                                    cellTmpl += '{{if isGroupingBy(record, "' + column.field + '")}}<i class="table-down-icon iconfont icon-xiangshang icon-shouqi"></i>{{/if}}';
                                    return cellTmpl + '</td>';
                                })
                                .join('');
                            me.rowTmpl = '<tr{{if isNode(record)}} data-node-id="{{getNodeId(record)}}"{{if isLeaf(record)}} class="group-table-inner"{{/if}}{{/if}}>';
                            if (me.hasCheckbox) {
                                me.rowTmpl += '<td class="grid-col-checkbox"  ng-style="dragStyle">';
                                me.rowTmpl += '<div class="form-clickbox {{if isCheckedRow(record)}} selected{{/if}}"><a href="javascript:void(0);" class="fi"></a></div>';
                                me.rowTmpl += '</td>';
                            }
                            me.rowTmpl += columnsTmpl;
                            me.rowTmpl += '</tr>';
                        }
                        return me.rowTmpl;
                    },

                    _getCompiledRowTmpl: function () {
                        var me = this;
                        if (!me.compiledRowTmpl) {
                            me.compiledRowTmpl = artTmpl.compile(me._getRowTmpl());
                        }
                        return me.compiledRowTmpl
                    },


                    _getCompiledBodyTmpl: function () {
                        var me = this;
                        if (!me.compiledTableTmpl) {
                            me.compiledTableTmpl = artTmpl.compile('<tbody>{{each source as record rowIndex}}' + me._getRowTmpl() + '{{/each}}</tbody>');
                        }
                        return me.compiledTableTmpl;
                    },

                    _registerHelpers: function () {
                        var me = this,
                            columns;
                        if (me.doneRegisterHelpers !== true) {
                            columns = me.columns;
                            _.chain(columns)
                                .map(_.property('data'))
                                .filter(_.negate(_.isEmpty))
                                .map(function (data) {
                                    var firstVLPos = data.indexOf('|'),
                                        filterString;
                                    if (firstVLPos !== -1) {
                                        filterString = data.substring(firstVLPos);
                                        return filterString.match(/\|\s*[\$_\w\d]+/g)
                                    }
                                })
                                .filter(_.negate(_.isEmpty))
                                .flatten()
                                .map(function (x) {
                                    return $.trim(x.substring(1));
                                })
                                .each(function (helperName) {
                                    artTmpl.helper(helperName, function () {
                                        return $filter(helperName).apply(me, arguments);
                                    });
                                });
                            artTmpl.helper('isNode', _.bind(me._isNode, me));
                            artTmpl.helper('getNodeId', _.bind(me._getNodeId, me));
                            artTmpl.helper('isGroupingBy', _.bind(me._isGroupingBy, me));
                            artTmpl.helper('isCheckedRow', _.bind(me._isCheckedRow, me));
                            artTmpl.helper('isLeaf', function (record) {
                                var node = me.dataSource.$node(record);
                                return node.isLeaf();
                            });
                            me.doneRegisterHelpers = true;
                        }
                    },

                    _syncRows: function (newSource, oldSource) {
                        if (_.isUndefined(newSource)) return;
                        var me = this,
                            scope = me.scope,
                            tbody = me.tbody,
                            trs = tbody.rows,
                            $rows = me.element.find('.table-body tr'),
                            added = Arrays.subtract(newSource, oldSource),
                            removed = Arrays.subtract(oldSource, newSource),
                            balanceCount = added.length - removed.length,
                            minRowIndex = $rows.length,
                            rowIndex, row, $newRow;
                        angular.forEach(removed, function (r) {
                            rowIndex = Arrays.indexOf(oldSource, r);
                            row = trs[rowIndex];
                            row.parentNode.removeChild(row);
                            minRowIndex = Math.min(minRowIndex, rowIndex);
                        });
                        angular.forEach(added, function (r) {
                            rowIndex = Arrays.indexOf(newSource, r);
                            $newRow = angular.element(tbody.insertRow(rowIndex));
                            $newRow.replaceWith(me._getCompiledRowTmpl()({
                                record: r,
                                rowIndex: rowIndex,
                                grid: me
                            }));
                            $compile(trs[rowIndex])(scope);
                            minRowIndex = Math.min(minRowIndex, rowIndex);
                        });
                        me._keepLazyViewLength(balanceCount);
                        me._refreshRows(minRowIndex);
                    },

                    _refreshRows: function (start, end) {
                        var me = this,
                            scope = me.scope,
                            gridOuterScope = scope.$parent,
                            trs = Array.prototype.slice.apply(me.tbody.rows, arguments),
                            ending = end || scope.source.length,
                            range = _.range(start, ending);
                        if (start >= 0 && start < ending) {
                            angular.forEach(trs, function (index, tr) {
                                angular.element(tr).toggleClass('table-body-tr-even', tr.rowIndex % 2 === 1)
                            });
                            gridOuterScope.$broadcast(CONSTANTS.REFRESH_ROWS, range);
                        }
                    },

                    _render: function () {
                        var me = this,
                            tmpl, bodyHtml;
                        me._renderCellsStyle();
                        me._registerHelpers();
                        tmpl = me._getCompiledBodyTmpl();
                        bodyHtml = tmpl(me);
                        angular.element(me.tbody).replaceWith(bodyHtml);
                        me.tbody = me.element.find('tbody')[0];
                    },

                    _handleToggleExpand: function ($event, $target) {
                        var me = this,
                            dataSource = me.dataSource,
                            trs = me.tbody.rows,
                            rowTmpl = me._getCompiledRowTmpl(),
                            isToggleChildren = $target.is('i.table-down-icon'),
                            isExpand, $row, nodeId, node, record, childrenCount, start, childrenRows;
                        var $td, tdIndex, isChecked;
                        if (isToggleChildren) {
                            $row = $target.closest('tr');
                            $td = $target.closest('td');
                            tdIndex = $td.index();
                            nodeId = $row.data('node-id');
                            node = dataSource.$node(nodeId);
                            record = node.origin;
                            isExpand = $target.hasClass('icon-xiangshang icon-shouqi');
                            if (isExpand && !$row.data('fetchedChildren')) {
                                isChecked = me._isCheckedRow(record);
                                var childrenGroupParams = me.getChildrenGroupParams(record);
                                dataSource.fetchChildren(nodeId, childrenGroupParams, function (children) {
                                    _.chain(children).reverse().each(function (record) {
                                        $row.after(rowTmpl({record: record}));
                                    });
                                });
                                $row.data('fetchedChildren', true);
                                $target.addClass('icon-xiangxia icon-zhankai').removeClass('icon-xiangshang icon-shouqi');
                            } else {
                                start = $row[0].rowIndex;
                                childrenCount = dataSource.countChildren(record);
                                childrenRows = Array.prototype.slice.call(trs, start, start + childrenCount);
                                if (isExpand) {
                                    var closedRows = $([]);
                                    _.each(childrenRows, function (children) {
                                        var $children = $(children);
                                        var $icon = $children.find('.table-down-icon');
                                        var childrenIsExpand = $icon.hasClass('icon-xiangshang icon-shouqi');
                                        if (childrenIsExpand) {
                                            closedRows = closedRows.add($children);
                                        }
                                        children.style.display = '';
                                    });
                                    closedRows.each(function (index, el) {
                                        var $el = $(el);
                                        if ($el.is(':hidden')) {
                                            return;
                                        }
                                        var tdIndex = $el.find('.table-down-icon').closest('td').index();
                                        var $currentTr = $el;
                                        var isEnd = false;
                                        while (!isEnd) {
                                            var $nextTr = $currentTr.next('tr');
                                            if ($nextTr.length) {
                                                var nextTdIndex = $nextTr.find('.table-down-icon').closest('td').index();
                                                if (nextTdIndex < 0 || nextTdIndex > tdIndex) {
                                                    $nextTr.hide();
                                                    $currentTr = $nextTr;
                                                } else {
                                                    isEnd = true;
                                                }
                                            } else {
                                                isEnd = true;
                                            }
                                        }
                                    });
                                    $target.addClass('icon-xiangxia icon-zhankai').removeClass('icon-xiangshang icon-shouqi');
                                } else {
                                    _.each(childrenRows, function (children) {
                                        children.style.display = 'none';
                                    });
                                    $target.addClass('icon-xiangshang icon-shouqi').removeClass('icon-xiangxia icon-zhankai');
                                }
                            }
                        }
                    },

                    /*============================
                     * 以下是树节点判断函数
                     *============================
                     */

                    _isNode: function (record) {
                        return !!this._getNodeId(record);
                    },

                    _getNodeId: function (record) {
                        try {
                            return this.dataSource.$id(record);
                        } catch (e) {
                        }
                    },

                    _isCheckedRow: function(record) {
                        var me = this;
                        var currentNodeId = me._getNodeId(record);
                        var isChecked = false;
                        _.forEach(me.scope.checkedRows, function(row, index) {
                            var nodeId = me._getNodeId(row);
                            if (nodeId === currentNodeId) {
                                isChecked = true;
                            }
                        });
                        return isChecked;
                    },

                    _isGroupingBy: function (record, colField) {
                        try {
                            var node = this.dataSource.$node(record);
                            return !node.isLeaf() && _.last(node.groupParams).property === colField;
                        } catch (e) {
                            return false;
                        }
                    },


                    _enterCols: function (cols) {
                        var me = this;
                        if (!me.__originalCols) {
                            me.__originalCols = me.columns;
                        }
                        me.columns = cols;
                        me.groups = evalGroups(cols);
                        me._reSortHeaders();
                        me._reSortFooters();
                        me._restColSettingsBtn();
                        me._restAllTmpl();
                        me.dataSource.setGroups(me.groups);
                        me.dataSource.doRequestData(1, {groups: [_.first(me.groups)]}, function () {
                            me._render();
                        });

                        function evalGroups(cols) {
                            return _.chain(cols)
                                .map(function (col) {
                                    if (col.group === true) {
                                        return {property: col.field};
                                    }
                                })
                                .filter(_.negate(_.isUndefined))
                                .value();
                        }
                    },

                    _restAllTmpl: function () {
                        delete this.rowTmpl;
                        delete this.compiledRowTmpl;
                        delete this.compiledTableTmpl;
                        delete this.doneRegisterHelpers;
                    },

                    /**
                     * 将表头按照表格设置（列排序、显示隐藏）重新渲染
                     * @private
                     */
                    _reSortHeaders: function () {
                        var me = this,
                            $headRow = me.element.find('thead tr:last'),
                            headers = $headRow.children('th');
                            tmpledCells = me.hasCheckbox ? _.rest(headers) : headers;
                        _.chain(tmpledCells)
                            .map(angular.element)
                            .map(function ($cell) {
                                return {
                                    $cell: $cell,
                                    idx: _.indexOf(me.columns, getColumn($cell))
                                }
                            })
                            .filter(function (x) {
                                if (x.idx === -1) {
                                    x.$cell.hide();
                                    return false;
                                }
                                x.$cell.show();
                                return true;
                            })
                            .sortBy(_.property('idx'))
                            .map(_.property('$cell'))
                            .reduce(function ($left, $right) {
                                return $right.insertAfter($left);
                            });

                        function getColumn($cell) {
                            return $cell.isolateScope().column;
                        }
                    },

                    _reSortFooters: function () {
                        var me = this,
                            $footerRow = me.element.find('tfoot > tr'),
                            footers = $footerRow.children('th'),
                            tmpledCells = me.hasCheckbox ? _.rest(footers) : footers,
                            pairs = _.map(tmpledCells, function (cell) {
                                "use strict";
                                var $cell = angular.element(cell),
                                    column = $cell.data(CONSTANTS.DATA_COLUMN_INSTANCE_KEY),
                                    idx = _.indexOf(me.columns, column);
                                return {
                                    $cell: $cell,
                                    idx: idx
                                };
                            });

                        _.chain(pairs)
                            .filter(_.matcher({idx: -1}))
                            .pluck('$cell')
                            .invoke('hide');

                        _.chain(pairs)
                            .filter(_.negate(_.matcher({idx: -1})))
                            .sortBy(_.property('idx'))
                            .pluck('$cell')
                            .invoke('show')
                            .reduce(function ($left, $right) {
                                return $right.insertAfter($left);
                            });
                    },


                    /**
                     * 获取请求子集的分组参数，
                     * @param parentRecord 父记录
                     */
                    getChildrenGroupParams: function (parentRecord) {
                        var me = this,
                            ds = me.dataSource,
                            allGroups = me.groups,
                            node = ds.$node(parentRecord),
                            parentGroups = node.groupParams,
                            groups = [],
                            last;
                        angular.copy(parentGroups, groups);
                        last = _.last(groups);
                        last.value = parentRecord[last.property];
                        if (groups.length < allGroups.length) {
                            groups[groups.length] = angular.copy(allGroups[groups.length]);
                        }
                        return groups;
                    },
                    _restColSettingsBtn: function () {
                        var me = this,
                            scope = me.scope,
                            $headRow, $headers;
                        if (!!scope.colSettingsKey) {
                            $headRow = me.element.find('thead > tr:last');
                            $headRow.find('i[data-col-settings]').remove();
                            $headers = $headRow.find('th:visible');
                            if (me.hasCheckbox) {
                                $headers = $(_.rest($headers));
                            }
                            $headers.first().append($compile('<i data-col-settings class="iconfont2 fi-set set-btn" ng-click="groupGrid._startSetting($event)"></i>')(scope));
                        }
                    },

                    _startSetting: function ($event) {
                        var me = this, menuElement;
                        ColGroupSettings._startSetting(me);
                        menuElement = angular.element(document.getElementById('dropdownMenu_' + me.scope.colSettingsKey));
                        menuElement.removeClass("open");
                        $event.stopPropagation();
                    },

                    _loadAndReSortHeadersIfStored: function () {
                        var me = this, colSettings;
                        if (me.scope.colSettingsKey) {
                            colSettings = LocalStorages.get(me.scope.colSettingsKey);
                        }
                        if (colSettings) {
                            me.columns = _.map(colSettings, function (colSetting) {
                                return me.__originalCols[colSetting.oIdx];
                            });
                        } else {
                            me.columns = me.__originalCols;
                        }
                        me._reSortHeaders();
                        me._reSortFooters();
                    },
                    _registerLayoutService: function () {
                        var me = this, menuDiv,
                            tableId = me.scope.colSettingsKey;
                        if (!tableId || tableId === "") {
                            return;
                        }
                        menuDiv = angular.element('<div data-target="dropdownMenu_' + tableId + '" context-menu="" class="ng-isolate-scope" data-hitarea="thead"></div>');
                        me.element.wrap($compile(menuDiv)(me.scope));
                        gridLayoutService.registerLayout(me.scope, me);
                    },

                    getLayout: function () {
                        var me = this,
                            curColSettings,
                            allColWidths = me._getAllColWidths();
                        curColSettings = _.map(me.columns, function (col) {
                            var idx = col.index;
                            var width = allColWidths[idx];
                            return {oIdx: idx, width: width, group: col.group};
                        });
                        return JSON.stringify({
                            layout: curColSettings,
                            sortName: me.sortName,
                            sortDirection: me.sortDirection
                        });
                    },

                    storeLayout: function (colSettingsJson) {
                        var me = this,
                            dataSource = me.dataSource;
                        if (!me.__originalCols) {
                            me.__originalCols = me.columns;
                        }
                        if (colSettingsJson) {
                            var layoutContent = JSON.parse(colSettingsJson);
                            me.colSettings = layoutContent.layout;
                            me.columns = _.map(me.colSettings, function (colSetting) {
                                return _.extend(me.__originalCols[colSetting.oIdx], {
                                    width: colSetting.width,
                                    group: colSetting.group
                                });
                            });
                            me.sortName = layoutContent.sortName;
                            me.sortDirection = layoutContent.sortDirection;
                        } else {
                            me.colSettings = {};
                            me.columns = _(me.__originalCols).map(function (col) {
                                delete col.width;
                                delete col.group;
                                return col;
                            });
                            me.sortName = [];
                            me.sortDirection = [];
                        }
                        if (dataSource) {
                            dataSource.sortName = me.sortName;
                            dataSource.sortDirection = me.sortDirection;
                            if (me.__rendered) {
                                dataSource.doRequestData();
                            }
                        } else {
                            GroupDataSources.get(me.attrs.sourceName).then(function (dataSource) {
                                dataSource.sortName = me.sortName;
                                dataSource.sortDirection = me.sortDirection;
                                if (me.__rendered) {
                                    dataSource.doRequestData();
                                }
                            });
                        }
                    },
                    reload: function () {
                        var me = this,
                            scope = me.scope;
                        me.scope.allChecked = false;
                        if (me._hasCheckedBox()) {
                            me.scope.checkedRows = [];
                        }
                        me._enterCols(me.columns);
                    },

                    _hasVerticalScroll: function () {
                        var me = this,
                            containerHeight = me.element.height(),
                            tableHeight = me.element.children('table').height();
                        return containerHeight < tableHeight;
                    },

                    _getContentWidth: function () {
                        var me = this,
                            gridWidth = me.element.width(),
                            gridContentWidth = gridWidth - 2;
                        if (me._hasVerticalScroll()) {
                            gridContentWidth -= CONSTANTS.SPACE_CELL_WIDTH
                        }
                        return gridContentWidth;
                    },

                    _getSingleColWidth: function (columnWidthDef, gridContentWidth) {
                        var widthPercent;
                        if (!!columnWidthDef) {
                            if (/^[\d\.]+%$/.test(columnWidthDef)) {
                                widthPercent = Number(columnWidthDef.substring(0, columnWidthDef.length - 1));
                                return gridContentWidth * widthPercent / 100;
                            } else if (/^[\d\.]+px$/.test(columnWidthDef)) {
                                return Number(columnWidthDef.substring(0, columnWidthDef.length - 2));
                            } else if (/^[\d\.]+$/.test(columnWidthDef)) {
                                return Number(columnWidthDef);
                            }
                        }
                    },

                    _getAllColWidths: function () {
                        var me = this,
                            __originalCols = me.__originalCols || me.columns,
                            gridContentWidth = me._getContentWidth(),
                            columnWidths,
                            colSettings = me.colSettings;
                        columnWidths = Arrays.transform(__originalCols, function (col) {
                            var oIdx = _.findIndex(me.__originalCols, col),
                                colSetting;
                            if (col.width) {
                                return col.width;
                            }
                            if (oIdx >= 0 && _.isArray(colSettings)) {
                                colSetting = _.findWhere(colSettings, {oIdx: oIdx});
                                if (colSetting && colSetting.width) {
                                    return colSetting.width;
                                }
                            }
                            return me._getSingleColWidth(col.widthDef, gridContentWidth);
                        });
                        paddingWithSpentAvg(columnWidths, gridContentWidth, CONSTANTS.MIN_CELL_WIDTH);
                        return columnWidths;

                    },

                    _getColWidthClassBlocks: function (allColWidths) {
                        var me = this,
                            colWidthClassBlocks = [],
                            colWidthClassName, colWidthClassBlock;
                        angular.forEach(allColWidths, function (colWidth, colIndex) {
                            if (me.hasCheckbox) {
                                colIndex++;
                            }
                            colWidthClassName = me._getColWidthClassName(colIndex);
                            colWidthClassBlock = _toWidthClassBlock(colWidthClassName, colWidth);
                            colWidthClassBlocks.push(colWidthClassBlock);
                        });
                        return colWidthClassBlocks;
                    },

                    _renderCellsStyle: function () {
                        var me = this,
                            element = me.element,
                            allColWidths = me._getAllColWidths(),
                            colWidthClassBlocks = me._getColWidthClassBlocks(allColWidths),
                            cellWidthStyleBlockContent = _.reduce(colWidthClassBlocks, Functions.sum),
                            $newCellStyleBlock = angular.element('<style>' + cellWidthStyleBlockContent + '</style>');
                        element.children('style').remove();
                        element.append($newCellStyleBlock);
                        me._reCalculateWidth();
                    },


                    _reCalculateWidth: function () {
                        var me = this,
                            groupGrid = me.element.children('table'),
                            allColWidths = _(me.columns).map(_.property('width')),
                            totalWidth = _.chain(allColWidths).map(Number).reduce(Functions.sum).value();
                        groupGrid.width(totalWidth);
                    },

                    _setColWidth: function () {
                        var me = this,
                            i, len, width, thElements;
                        thElements = me.$headerBox.find("th:visible");
                        for (i = 0, len = me.columns.length; i < len; i++) {
                            width = angular.element(thElements[i]).width();
                            me.columns[i].width = width;
                        }
                    },
                    _getColWidthClassName: function (startColIndex) {
                        return 'grid-' + this.hash + '-col-' + startColIndex;
                    }
                };


                return {
                    restrict: 'E',
                    template: '<div class="group-table" ng-click="gridClick($event)" ng-dblclick="gridDbClick($event);">\n    <table class="com-table" border="0" cellspacing="0" cellpadding="0">\n        <thead>\n            <tr>\n            </tr>\n        </thead>\n        <tbody ng-dblclick="bodyDbClick($event)">\n        </tbody>\n    </table>\n    <div class="col-resize-line"></div>\n</div>',
                    replace: true,
                    transclude: true,
                    require: 'gGroupGrid',
                    scope: {
                        sourceName: '@',
                        colSettingsKey: '@',
                        selectedRow: '@',
                        checkedRows: '=checkedRows',
                        onBeforeRowDbclick: '&',
                        onRowDbclick: '&'
                    },
                    compile: function (tEle, tAttrs, transclude) {
                        var $head = tEle.find('thead tr');
                        return function (scope, ele, attrs, groupGrid) {

                            //监听筛选器
                            scope.$watch('filter', function (newFilters) {
                                var dataSource = groupGrid.dataSource,
                                    dataSourceProxy = dataSource.pageDataSourceProxy,
                                    params = {
                                        filterName: [],
                                        filterValue: []
                                    };
                                if (dataSourceProxy) {
                                    _.each(newFilters, function (filterVal, filterName) {
                                        if (filterVal === 0 || !!filterVal) {
                                            params.filterName.push(filterName);
                                            params.filterValue.push(filterVal);
                                        }
                                    });
                                    dataSourceProxy.params = _.extendOwn(params, _.omit(dataSourceProxy.params, 'filterName', 'filterValue'));
                                    if (!dataSource.allowAutoLoad) {
                                        dataSource.doRequestData();
                                    }
                                }
                            }, true);

                            scope.draggable = attrs.draggable != "false";

                            scope.$on(scope.sourceName, function (event, result) {
                                groupGrid.__rendered = true;
                                groupGrid.source = result['records'];
                                groupGrid._render();
                                if (result.hasOwnProperty('moreAttrs')) {
                                    scope.moreAttrs = result['moreAttrs'];
                                }
                                if (groupGrid._hasCheckedBox()) {
                                    groupGrid.scope.checkedRows = [];
                                    groupGrid._updateRowCheckbox();
                                }
                            });

                            groupGrid.dataSource = GroupDataSources.get(attrs.sourceName);

                            transclude(scope, function (clone) {
                                var $foot = clone.filter('tfoot');
                                angular.forEach(clone, function (obj) {
                                    if (obj.tagName === 'TH') {
                                        $head.append(obj);
                                    }
                                });

                                if ($foot.length > 0) {
                                    // init footData
                                    ele.children('table').append($foot);
                                }
                            }, groupGrid);

                            groupGrid._restColSettingsBtn();
                            groupGrid._registerHeaderDraggable();
                            groupGrid._registerLayoutService();
                            scope.gridClick = function ($event) {
                                var $target = angular.element($event.target);
                                groupGrid._handleToggleExpand($event, $target);
                                groupGrid._headClick($event);
                                if (groupGrid._hasCheckedBox()) {
                                    groupGrid._toggleRowChecked($event, $target);
                                    groupGrid._toggleAllChecked($event, $target);
                                }
                            };
                            scope.gridDbClick = function ($event) {
                                var target = $event.target || $event.srcElement;
                                    $target = angular.element(target);
                                if (!$target.closest('tbody').is(groupGrid.element.find('tbody'))) {
                                    return;
                                }
                                var $cell = $target.closest('td'),
                                    $row = $cell.closest('tr');
                                groupGrid._rowDbClick($event, $row);
                            }
                        };
                    },
                    controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
                        $scope.groupGrid = new GroupGrid($scope, $element, $attrs);
                        return $scope.groupGrid;
                    }]
                }
            }
        )
        .directive('gGroupCheckboxColumn', function () {
            return {
                restrict: 'E',
                template: '<th class="grid-col-checkbox"><g-checkbox ng-model="allChecked"></g-checkbox></th>',
                replace: true,
                scope: false,
                require: ['^gGroupGrid'],
                link: function (scope, element, attrs, controllers) {
                    var gridController = controllers[0],
                        gItemDisabled = controllers[1];
                    gridController.hasCheckbox = true;
                    gridController.checkboxesDisabledController = gItemDisabled;
                    /** @namespace attrs.gItemDisabled */
                    gridController.checkboxesDisabledExpress = attrs.gItemDisabled;
                }
            };
        })
        .directive('gGroupColumn', function (Arrays) {

            function getRealField(rawData) {
                var firstFilterIdx;
                if (rawData) {
                    firstFilterIdx = rawData.indexOf('|');
                    if (firstFilterIdx !== -1) {
                        return $.trim(rawData.substring(0, firstFilterIdx));
                    }
                }
                return rawData;
            }

            function formatDataFilters(rawData) {
                if (!!rawData) {
                    return rawData.replace(/\s*\|\s*/g, ' | ');
                }
                return rawData;
            }

            function getCellAlignClass(scope) {
                return 'align-' + (scope.cellAlign || scope.align || 'left');
            }

            return {
                template: '<th>\n    <div class="grid-head-sort" ng-if="sortable !== undefined">\n        <button class="btn">\n            <span ng-class="{\'selected\': grid._isSort(sortable, \'asc\')}" class="caret caret-up"></span>\n        </button>\n        <button class="btn">\n            <span ng-class="{\'selected\': grid._isSort(sortable, \'desc\')}" class="caret caret-down"></span>\n        </button>\n    </div>\n    <sub ng-if="grid._sortIndex(sortable)!==\'\'">{{grid._sortIndex(sortable)}}</sub>\n</th>',
                replace: true,
                restrict: 'E',
                transclude: true,
                scope: {
                    $sortable: '@sortable',
                    data: '@',
                    width: '@',
                    filter: '@',
                    tmpl: '@',
                    align: '@',
                    cellAlign: '@'
                },
                require: '^gGroupGrid',
                compile: function (tElement, tAttrs, transclude) {
                    return function (scope, element, attrs, gridController) {
                        transclude(scope, function (clone) {
                            angular.forEach(clone, function (node) {
                                if (!/G-COLUMN-TMPL|G-COLUMN-EDITOR|G-GROUP-COLUMN-FILTER/i.test(node.tagName)) {
                                    element.append(node);
                                }
                            });
                        }, gridController);
                        var columnInstance = {
                                field: getRealField(scope.data),
                                data: formatDataFilters(scope.data),
                                text: $.trim(element.text()),
                                widthDef: scope.width,
                                tmpl: scope.tmpl,
                                filter: scope.filter,
                                filterTmpl: scope.filterTmpl,
                                filterCondition: scope.filterCondition,
                                element: element,
                                cellAlignClass: getCellAlignClass(scope)
                            },
                            newLength = gridController._addColumn(columnInstance),
                            colIndex = newLength - 1,
                            cellIndex = element[0].cellIndex,
                            onBeforeCellDbclickDef = attrs.onBeforeCellDbclick,
                            onCellDbclickDef = attrs.onCellDbclick,
                            sortable;
                        // 如果需要排序， data中有空格视为包含过滤器， 截取空格前字符串作为排序字段
                        if (attrs.hasOwnProperty('sortable')) {
                            sortable = columnInstance.field;
                        }

                        if (!!scope.editorTmpl) {
                            columnInstance.editorTmpl = scope.editorTmpl;
                        }
                        // 列双击事件
                        if (onBeforeCellDbclickDef) {
                            columnInstance.onBeforeCellDbclick = gridController._getScopeEvent(onBeforeCellDbclickDef);
                        }
                        if (onCellDbclickDef) {
                            columnInstance.onCellDbclick = gridController._getScopeEvent(onCellDbclickDef);
                        }

                        columnInstance.colWidthClassName = gridController._getColWidthClassName(cellIndex);
                        columnInstance.index = colIndex;
                        columnInstance.sortable = scope.sortable = sortable;

                        scope.column = columnInstance;
                        scope.grid = gridController;
                        attrs.$addClass(columnInstance.colWidthClassName);

                        //拖拽功能 *****begin*******
                        if (gridController.scope.draggable) {
                            element.attr("draggable", "true");
                            element[0].addEventListener("dragover", function (event) {
                                event.preventDefault();
                            });
                            element[0].addEventListener("drop", function (event) {
                                event.preventDefault();
                                var begin_column = gridController.element.data("_begin_column");
                                if (!begin_column) {
                                    return;
                                }
                                if (begin_column.group != scope.column.group) {
                                    return;
                                }
                                if (Arrays.indexOf(gridController.columns, begin_column) <= Arrays.indexOf(gridController.columns, scope.column)) {
                                    gridController.columns = Arrays.moveAfter(gridController.columns, begin_column, scope.column);
                                } else {
                                    gridController.columns = Arrays.moveBefore(gridController.columns, begin_column, scope.column);
                                }
                                if (gridController._hasCheckedBox()) {
                                    scope.grid.scope.checkedRows = [];
                                }
                                gridController._enterCols(gridController.columns);
                            });
                            element[0].ondragstart = function (event) {
                                gridController.element.data("_begin_column", columnInstance);
                            };
                        }
                        //拖拽功能 *****end*******
                    };
                }
            };
        })
        .directive('gGroupColumnFilter', function () {
            return {
                restrict: 'E',
                scope: false,
                compile: function (tElement, tAttrs) {
                    var filterTmpl = $.trim(tElement.html()),
                        condition = tAttrs.condition;
                    if (navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion.split(";")[1].replace(/[ ]/g, "") == "MSIE8.0") {
                        filterTmpl = filterTmpl.replace('<:', '<').replace('<:/', '</');
                    }
                    return function (scope, element) {
                        scope.filterTmpl = filterTmpl;
                        scope.filterCondition = condition;
                    }
                }
            };
        })
        .directive('cellTemplated', function ($compile) {
            return {
                restrict: 'A',
                scope: true,
                require: '^gGroupGrid',
                link: function (scope, element, attrs, grid) {
                    //noinspection JSUnresolvedVariable
                    var columnIndex = grid._getColumnIndex(element),
                        tr = element.parent()[0],
                        rowIndex = tr.rowIndex,
                        row = grid.scope.source[rowIndex],
                        column = grid.columns[columnIndex],
                        $displayPlace = element.children('[data-role=display]'),
                        tmplScope = grid._getCellTmplScope({
                            rowIndex: rowIndex,
                            column: column,
                            row: row,
                            columnIndex: columnIndex
                        });
                    $displayPlace.html(column.tmpl);
                    $compile($displayPlace)(tmplScope);
                    tmplScope.$on(CONSTANTS.REFRESH_ROWS, function (event, range) {
                        if (_.contains(range, tmplScope.rowIndex)) {
                            tmplScope.rowIndex = tr.rowIndex;
                        }
                    });
                }
            };
        })
        .directive('gGroupFooter', function () {
            return {
                restrict: 'E',
                template: '<tfoot><tr></tr></tfoot>',
                replace: true,
                transclude: true,
                scope: false,
                compile: function (tElement, tAttrs, transclude) {
                    return function (scope, element, attrs, gridController) {
                        var $footRow = element.children('tr');
                        transclude(scope, function (clone) {
                            $footRow.append(clone.filter('th'));
                        }, gridController);
                    }
                }
            };
        })
        .directive('gGroupFooterCell', function () {
            return {
                restrict: 'E',
                template: '<th><div ng-transclude/> </th>',
                replace: true,
                transclude: true,
                scope: false,
                require: '^gGroupGrid',
                link: function (scope, element, attrs, groupGrid) {
                    "use strict";
                    var colIdx = element[0].cellIndex;
                    if (!groupGrid.hasCheckbox) {
                        element.data(CONSTANTS.DATA_COLUMN_INSTANCE_KEY, groupGrid.columns[colIdx]);
                        attrs.$addClass(groupGrid._getColWidthClassName(colIdx));
                    } else {
                        if (colIdx === 0) {
                            attrs.$addClass('grid-col-checkbox');
                        } else {
                            element.data(CONSTANTS.DATA_COLUMN_INSTANCE_KEY, groupGrid.columns[colIdx - 1]);
                            attrs.$addClass(groupGrid._getColWidthClassName(colIdx));
                        }
                    }
                }
            };
        });
});